package com.generalbackend.task;

import com.generalbackend.common.annotion.ApiPermission;
import com.generalbackend.common.enums.MenuEnum;
import com.generalbackend.common.util.SnowFlakeIdGeneratorUtil;
import com.generalbackend.common.model.entity.*;
import com.generalbackend.common.service.*;
import com.generalbackend.role.model.entity.*;
import com.generalbackend.role.service.*;
import com.generalbackend.role.util.PermissionXmlParseUtil;
import com.generalbackend.role.util.RoleConf;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import static com.generalbackend.common.constants.Constants.*;

/**
 * @author tim.xie
 * @date 2021/12/13 10:43
 */
@Slf4j
@Component
@Order(value = 1)
@RestController
public class InitDbTask implements CommandLineRunner {

    @Autowired
    private CommonConfigService commonConfigService;
    @Autowired
    private IPermissionService permissionService;
    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private IRestApiService restApiService;
    @Autowired
    private IPermissionRestApiService permissionRestApiService;
    @Autowired
    private IUserService userService;
    @Autowired
    private PasswordEncoder pw;
    @Autowired
    private IRoleService roleService;
    @Autowired
    private IRolePermissionService rolePermissionService;

    @Override
    public void run(String... args) {
        try {
            init();
        } catch (IOException e) {
            log.error("XML file reading exception", e);
        } catch (DocumentException e) {
            log.error("XML parsing exception", e);
        }
    }

    public void init() throws IOException, DocumentException {
        File permissionFile = new File(MENU_PATH);
        String menuMD5 = DigestUtils.md5Hex(new FileInputStream(permissionFile));
        CommonConfig menuConfig = commonConfigService.getConfigByPrefixAndKey(MENU_PREFIX, MENU_MD5_KEY);
        if(menuConfig == null) {
            updatePermission();
            commonConfigService.insert(createMenuConfig(menuMD5));
        } else if (menuConfig != null && !menuConfig.getConfigValue().equals(menuMD5)) {
            updatePermission();
            commonConfigService.updateConfigValueByPrefixAndKey(MENU_PREFIX, MENU_MD5_KEY, menuMD5);
        }
        log.info("==================菜单初始化完成==================");
        // 初始化权限
        initPermission();
        // 初始化内置权限
        initUser();
    }

    private void initPermission() {
        clearPermissionDB();
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        ArrayList<PermissionRestApi> permissionRestApis = new ArrayList<>();
        String[] allroleApi = RoleConf.getPermissionUrl("allrole").split(",");
        String[] commonApi = RoleConf.getPermissionUrl("common").split(",");
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = mapping.getHandlerMethods();
        for(Map.Entry<RequestMappingInfo, HandlerMethod> rmi : handlerMethods.entrySet()) {
            HandlerMethod value = rmi.getValue();
            RestApi restApi = new RestApi();
            AtomicReference<MenuEnum[]> menuReference = new AtomicReference<>();
            Arrays.stream(value.getMethod().getDeclaredAnnotations()).forEach(e -> {
                if (e instanceof ApiOperation) {
                    ApiOperation apiOperation = (ApiOperation) e;
                    restApi.setDescription(apiOperation.notes());
                    restApi.setName(apiOperation.value());
                }
                if (e instanceof ApiPermission) {
                    ApiPermission apiPermission = (ApiPermission) e;
                    if (StringUtils.isEmpty(restApi.getDescription())) {
                        restApi.setDescription(apiPermission.desc());
                    }
                    if (StringUtils.isEmpty(restApi.getName())) {
                        restApi.setName(apiPermission.name());
                    }
                    restApi.setIsAll(apiPermission.isAll() ? 1 : 0);
                    restApi.setIsCommon(apiPermission.isCommon() ? 1 : 0);
                    menuReference.set(apiPermission.menu());
                }
            });
            RequestMappingInfo key = rmi.getKey();
            String url = String.join(",", key.getPatternsCondition().getPatterns());
            String method = String.join(",", key.getMethodsCondition().getMethods().stream().map(Enum::name).collect(Collectors.toList()));
            // 匹配是否在role.conf中为开放接口或免登接口
            int isAll = Arrays.stream(allroleApi).anyMatch(url::contains) ? 1 : 0;
            int isCommon = Arrays.stream(commonApi).anyMatch(url::contains) ? 1 : 0;
            restApi.setId(SnowFlakeIdGeneratorUtil.nextId());
            restApi.setUrl(url);
            restApi.setMethod(method);
            restApi.setIsAll(restApi.getIsAll() != null && restApi.getIsAll() == 1 ? restApi.getIsAll() : isAll);
            restApi.setIsCommon(restApi.getIsCommon() != null && restApi.getIsCommon() == 1 ? restApi.getIsCommon() : isCommon);
            restApiService.insert(restApi);
            if(menuReference.get() != null) {
                for (MenuEnum menuEnum : menuReference.get()) {
                    permissionRestApis.add(new PermissionRestApi(menuEnum.toString(), restApi.getId()));
                }
            }
            // role.conf
            for (String permission : RoleConf.getPermissionByApi(url)) {
                permissionRestApis.add(new PermissionRestApi(permission, restApi.getId()));
            }
        }
        permissionRestApiService.saveBatch(permissionRestApis);
        log.info("==================API权限初始化完成==================");
    }

    private void updatePermission() throws DocumentException, IOException {
        SAXReader reader = new SAXReader();
        Document document = reader.read(new File(MENU_PATH));
        Element root = document.getRootElement();
        List<Permission> list = PermissionXmlParseUtil.getPermissions(root, null);
        permissionService.updatePermissionTable(list);
    }

    private CommonConfig createMenuConfig(String menuMD5) {
        CommonConfig menuConfig = new CommonConfig();
        menuConfig.setId(SnowFlakeIdGeneratorUtil.nextId());
        menuConfig.setPrefix(MENU_PREFIX);
        menuConfig.setConfigKey(MENU_MD5_KEY);
        menuConfig.setConfigValue(menuMD5);
        menuConfig.setDefaultValue(menuMD5);
        menuConfig.setDescription(MENU_DESCRIPTION);
        return menuConfig;
    }

    private void clearPermissionDB() {
        permissionRestApiService.remove(null);
        restApiService.remove(null);
    }

    private void initUser() {
        if (!userService.exit(ADMIN_ID)) {
            insetUser(ADMIN_ID, ADMIN, ADMIN_DEFAULT_PASSWORD, ADMIN_NAME, ADMIN_ID);
        }
        if (!userService.exit(USER_ADMIN_ID)) {
            insetUser(USER_ADMIN_ID, USER_ADMIN, USER_ADMIN_DEFAULT_PASSWORD, USER_ADMIN_NAME, USER_ADMIN_ID);
        }
        if (!roleService.exit(ADMIN_ID)) {
            insetRole(ADMIN_ID,ADMIN_NAME,ADMIN_DESCRIPTION);
        }
        if (!roleService.exit(USER_ADMIN_ID)) {
            insetRole(USER_ADMIN_ID,USER_ADMIN_NAME,USER_ADMIN_DESCRIPTION);
        }

        List<Permission> permissions = permissionService.allPermission();
        String adminStr = "", useradminStr = "";
        for (int i = 0; i < permissions.size(); i++) {
            Permission permission = permissions.get(i);
            if (permission.getId().equals(MenuEnum.RoleManageMain.name())
                    ||permission.getId().equals(MenuEnum.RoleManage.name())
                    ||permission.getId().equals(MenuEnum.UserManage.name())) {
                useradminStr += permission.getId() + ",";
            } else if(permission.getId().equals(MenuEnum.Home.name())) {
                adminStr += permission.getId() + ",";
                useradminStr += permission.getId() + ",";
            }else {
                adminStr += permission.getId() + ",";
            }
        }
        ArrayList<Long> ids = new ArrayList<>();
        ids.add(ADMIN_ID);
        ids.add(USER_ADMIN_ID);
        rolePermissionService.deleteRolePermissionByRoleIds(ids);
        rolePermissionService.permissionUpdate(adminStr, null, ADMIN_ID);
        rolePermissionService.permissionUpdate(useradminStr, null, USER_ADMIN_ID);
    }

    private void insetUser(Long userId, String name, String password, String realName, Long roleId){
        User user = new User();
        user.setCreateUser(DEFAULT_CREATE_USER);
        user.setUpdateUser(DEFAULT_CREATE_USER);
        user.setId(userId);
        user.setName(name);
        user.setPassword(pw.encode(password));
        user.setRealName(realName);
        user.setRoleId(roleId);
        userService.insert(user);
    }
    
    private void insetRole(Long roleId, String name, String description) {
        Role role = new Role();
        role.setCreateUser(DEFAULT_CREATE_USER);
        role.setUpdateUser(DEFAULT_CREATE_USER);
        role.setId(roleId);
        role.setName(name);
        role.setDescription(description);
        roleService.insert(role);
    }
}
